/**
 *
 *  Logger.cc
 *  An Tao
 *
 *  Public header file in trantor lib.
 *
 *  Copyright 2018, An Tao.  All rights reserved.
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the License file.
 *
 *
 */

#include <trantor/utils/Logger.h>
#include <stdio.h>
#include <thread>
#ifndef _WIN32
#include <unistd.h>
#include <sys/syscall.h>
#else
#include <sstream>
#endif
#ifdef __FreeBSD__
#include <pthread_np.h>
#endif


#include "spdlog/fmt/ostr.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/sinks/daily_file_sink.h"
#include "spdlog/async.h"
#include "spdlog/sinks/basic_file_sink.h"
#include <stack>

#ifdef WIN32
#include <direct.h>
#include <io.h>
#else
#include <unistd.h>
#endif // WIN32

namespace trantor {

    static int createPath(const std::string& path) {
        auto tmpPath = path;
        std::stack<std::string> pathStack;
#ifdef _WIN32
        while (_access(tmpPath.c_str(), 06) != 0)
#else
        while (access(tmpPath.c_str(), F_OK) != 0)
#endif
        {
            if (tmpPath == "./" || tmpPath == "/")
                return -1;
            while (tmpPath[tmpPath.length() - 1] == '/')
                tmpPath.resize(tmpPath.length() - 1);
            auto pos = tmpPath.rfind('/');
            if (pos != std::string::npos) {
                pathStack.push(tmpPath.substr(pos));
                tmpPath = tmpPath.substr(0, pos + 1);
            } else {
                pathStack.push(tmpPath);
                tmpPath.clear();
                break;
            }
        }
        while (pathStack.size() > 0) {
            if (tmpPath.empty()) {
                tmpPath = pathStack.top();
            } else {
                if (tmpPath[tmpPath.length() - 1] == '/') {
                    tmpPath.append(pathStack.top());
                } else {
                    tmpPath.append("/").append(pathStack.top());
                }
            }
            pathStack.pop();

#ifdef _WIN32
            if (_mkdir(tmpPath.c_str()) == -1)
#else
            if (mkdir(tmpPath.c_str(), 0755) == -1)
#endif
            {
                LOG_ERROR << "Can't create path:" << path;
                return -1;
            }
        }
        return 0;
    }

    std::once_flag log_flag;
    LoggerPtr getLogger(const std::string& name, int logLevel, bool showConsole) {

        auto iterFind = spdlog::get(name);
        if (iterFind) {
            return iterFind;
        }

        std::call_once(log_flag, [] {
            createPath("logs");
            spdlog::init_thread_pool(1024, 1);
            spdlog::flush_every(std::chrono::seconds(3));
        });

        spdlog::level::level_enum lv = (spdlog::level::level_enum)logLevel;

        std::shared_ptr<spdlog::sinks::stdout_color_sink_mt> console_sink;
        if (showConsole) {
            console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
        }

        spdlog::filename_t basename = "logs/" + name;
        auto combined_logger = spdlog::create_async<spdlog::sinks::daily_file_sink_mt>(name, basename, 23, 59);

#ifndef DEBUG
        combined_logger->sinks()[0]->set_level(spdlog::level::trace);
#else
        combined_logger->sinks()[0]->set_level(spdlog::level::err);
#endif // DEBUG

        if (console_sink) {
            console_sink->set_level(spdlog::level::info);
            combined_logger->sinks().push_back(console_sink);
        }

        combined_logger->set_level(lv);
        combined_logger->set_pattern("%Y-%m-%d %H:%M:%S.%e %t %^%l%$ %n %v  - %s:%#");
        spdlog::set_default_logger(combined_logger);

        spdlog::flush_on(spdlog::level::err);
        spdlog::flush_every(std::chrono::seconds(3));
        return combined_logger;
    }

    const char *strerror_tl(int savedErrno) {
#ifndef _MSC_VER
        return strerror(savedErrno);
#else
        static thread_local char errMsg[64];
        (void)strerror_s<sizeof errMsg>(errMsg, savedErrno);
        return errMsg;
#endif
    }

    LoggerInst& LoggerInst::instance() {
        static LoggerInst inst;
        return inst;
    }

    LoggerPtr LoggerInst::getLogger() {
        if (m_defLogger) {
            return m_defLogger;
        }
        bool showConsole = true;
#ifndef WIN32
        showConsole = false;
#endif // WIN32

        m_defLogger = createLogger("system", 0, showConsole, true);
        return m_defLogger;
    }

    LoggerPtr LoggerInst::createLogger(const std::string& name, int logLevel, bool showConsole, bool defLogger) {

        auto loggerPtr = trantor::getLogger(name, logLevel, showConsole);
        if (defLogger) {
            spdlog::set_default_logger(loggerPtr);
        }
        return loggerPtr;

    }

}  // namespace trantor
using namespace trantor;

Logger::Logger(const char* file, int line):  level_(kInfo), source_loc_(file, line, nullptr) {
}
Logger::Logger(const char* file, int line, LogLevel level) :  level_(level), source_loc_(file, line, nullptr) {
}
Logger::Logger(const char* file, int line, LogLevel level, const char *func) : level_(level), source_loc_(file, line, func) {
}
Logger::Logger(const char* file, int line, bool) : level_(kFatal), source_loc_(file, line, nullptr) {
    if (errno != 0) {
        logStream_ << strerror_tl(errno) << " (errno=" << errno << ") ";
    }
}

Logger::~Logger() {
    if (logStream_.bufferLength() == 0) {
        return;
    }

    auto loggerPtr = LoggerInst::instance().getLogger();
    if (loggerPtr) {
        spdlog::string_view_t logV(logStream_.bufferData(), logStream_.bufferLength());
        loggerPtr->log(source_loc_, (spdlog::level::level_enum)level_, logV);
    }
}

LogStream &Logger::stream() {
    return logStream_;
}
